package com.flex.showdoc4j.executor;

import com.flex.core.annotation.Element;
import com.flex.core.enums.ParamType;
import com.flex.showdoc4j.model.DocModel;
import com.flex.showdoc4j.model.ReturnVariableModel;
import com.flex.showdoc4j.reflection.FieldReflection;
import org.springframework.beans.BeanUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * @author ganlt<br>
 * @since 2019/06/27 13:54 <br>
 */
public class ReturnVariable {
    private Wrapper wrapper;

    public ReturnVariable(Wrapper wrapper) {
        this.wrapper = wrapper;
    }

    /**
     * 解析返回参数
     * @param result  解析返回参数
     */
    public void wrapperReturnVariable(Object result) {
        if (result == null) {
            return;
        }
        DocModel docModel = wrapper.getDocModel();
        Object returnObj;
        if (docModel.isPage()) {
            returnObj = pageExtraObject(result);
        } else {
            returnObj = FieldReflection.getDescriptor(result, Wrapper.RETURN_ATTRIBUTE_NAME);
        }

        List<ReturnVariableModel> returnVariables = returnVariables(returnObj);
        wrapper.setReturnParamMap(returnVariables);
    }

    /**
     * 获取所有参数并封装
     * @return 返回对象列表
     */
    private List<ReturnVariableModel> returnVariables(Object result) {
        List<ReturnVariableModel> returnVariables = new ArrayList<>();
        if (result == null) {
            return returnVariables;
        }
        if (result instanceof List || result instanceof Set) {
            List resultList = (List) result;
            if (!resultList.isEmpty()) {
                result = resultList.get(0);
            } else {
                return returnVariables;
            }
        }
        PropertyDescriptor[] propertyDescriptors = null;
        if (result instanceof Class) {
            propertyDescriptors = BeanUtils.getPropertyDescriptors((Class<?>) result);
        } else {
            propertyDescriptors = BeanUtils.getPropertyDescriptors(result.getClass());
        }
        List<Field> fields = FieldReflection.getFields(result, true);
        for (PropertyDescriptor descriptor : propertyDescriptors) {
            String name = descriptor.getName();
            if (!"class".equals(name)) {
                Type type = descriptor.getPropertyType();
                ReturnVariableModel returnVariableModel = variableBind(name, type, fields);
                returnVariables.add(returnVariableModel);
            }

        }

        return returnVariables;
    }

    /**
     * 分页提取实际对象
     * @param result 分页对象
     * @return 返回分页实际存储对象
     */
    private Object pageExtraObject(Object result) {
        if (result == null) {
            return null;
        }

        Object contentObject = FieldReflection.getDescriptor(result, Wrapper.RETURN_ATTRIBUTE_NAME);
        if (contentObject != null) {
            return FieldReflection.getDescriptor(contentObject, Wrapper.RETURN_PAGE_ATTRIBUTE_NAME);
        }
        return null;
    }



    private ReturnVariableModel variableBind(String name, Type type, List<Field> fields) {
        ReturnVariableModel returnVariableModel = new ReturnVariableModel();
        returnVariableModel.setName(name);
        returnVariableModel.setType(type.getTypeName());
        if (fields == null) {
            return returnVariableModel;
        }

        for (Field field : fields) {
            String fieldName = field.getName();
            if (!fieldName.equalsIgnoreCase(name)) {
                continue;
            }

            String typeStr = typeConvert(field);
            returnVariableModel.setType(typeStr);

            Element element = field.getAnnotation(Element.class);
            if (element == null) {
                return returnVariableModel;
            }
            boolean recursion = element.recursion();
            String eleDesc = element.desc();
            String eleName = element.name();
            returnVariableModel.setName(eleName);
            returnVariableModel.setDesc(eleDesc);
            if (recursion) {
                Type childType = field.getGenericType();
                if (childType instanceof Class) {
                    List<ReturnVariableModel> returnVariableModels = returnVariables(childType);
                    returnVariableModel.setChildModel(returnVariableModels);
                }
                else if (((ParameterizedType) childType).getRawType() == List.class) {
                    Type actualType = getListType(childType);
                    List<ReturnVariableModel> returnVariableModels = returnVariables(actualType);
                    returnVariableModel.setChildModel(returnVariableModels);
                } else {
                    List<ReturnVariableModel> returnVariableModels = returnVariables(field.getType());
                    returnVariableModel.setChildModel(returnVariableModels);
                }
            }

            return returnVariableModel;
        }

        return returnVariableModel;
    }

    private Type getListType(Type type) {
        Type[] resultArgType = null;
        Type resultType = type;
        if (resultType instanceof ParameterizedType
                && ((ParameterizedType) resultType).getRawType() == java.util.List.class){
            resultArgType = ParameterizedType.class.cast(resultType).getActualTypeArguments();
        }

        return (resultArgType!=null && resultArgType.length>0)?resultArgType[0]:null;
    }

    private String typeConvert(Field field) {
        if (field.getType().isAssignableFrom(Long.class) || field.getType().isAssignableFrom(long.class)) {
            return ParamType.LONG.getVal();
        } else if (field.getType().isAssignableFrom(Integer.class)
                || field.getType().isAssignableFrom(int.class)
                || field.getType().isAssignableFrom(BigInteger.class)) {
            return ParamType.INT.getVal();
        } else if (field.getType().isAssignableFrom(Double.class)
                || field.getType().isAssignableFrom(double.class)
                || field.getType().isAssignableFrom(BigDecimal.class)) {

            return ParamType.DOUBLE.getVal();
        } else if (field.getType().isAssignableFrom(String.class)) {
            return ParamType.STRING.getVal();
        } else if (field.getType().isAssignableFrom(Collections.class)) {
            return ParamType.LIST.getVal();
        } else if (field.getType().isAssignableFrom(Map.class)) {
            return ParamType.MAP.getVal();
        } else {
            return ParamType.MAP.getVal();
        }
    }





}
